package application

import (
	"encoding/json"
	"fmt"
	"github.com/duke-git/lancet/v2/slice"
	log "github.com/sirupsen/logrus"
	"runtime"
	"strings"
	"sync"
	"time"
	"usbipproxy/common"
	"usbipproxy/domain/dto"
	"usbipproxy/domain/service"
	"usbipproxy/domain/tcpsession"
	"usbipproxy/domain/usbipserversrv"
	"usbipproxy/domain/usbipstatusmgr"
	"usbipproxy/domain/vo"
	"usbipproxy/pb"
)

/*
windows:
C:\Windows\System32>usbipd list
Connected:
BUSID  VID:PID    DEVICE                                                        STATE
2-2    0000:3825  USB 输入设备                                                  Not shared
2-3    0bda:4853  Realtek Bluetooth Adapter                                     Not shared
3-1    174f:1811  Integrated Camera, Integrated IR Camera, Camera DFU Device    Not shared

Persisted:
GUID                                  DEVICE
a812be47-6387-4de1-8ef4-8587cbfc96c9  USB 输入设备

usbipd: warning: The VBoxUsbMon driver is not correctly installed; a repair or re-install of this software should fix that.

linux:
root@zxf1:~# usbip list -l

  - busid 3-1 (0e0f:0003)
    VMware, Inc. : Virtual Mouse (0e0f:0003)

  - busid 4-1 (3535:6300)
    unknown vendor : unknown product (3535:6300)
*/
type HttpMsgHandleServiceImpl struct {
	tcpClientSvc      TcpClientService
	usbIpServerCmd    service.UsbIpServerCmdService
	usbipServerCliMgr usbipserversrv.UsbipServerSrvMgrService
	tcpsessionMgr     tcpsession.TcpSessionMgrRepo
	deviceSvc         service.DeviceService
	statusMgr         usbipstatusmgr.UsbIpStatusMgrService
}

func (impl *HttpMsgHandleServiceImpl) UsbipUnbind(req *dto.UsbipUnbindReq) (*dto.UsbipUnbindRsp, error) {
	log.Infof("UsbipUnbind req:%#v", req)
	if req.BusId != "" {
		impl.usbIpServerCmd.UnBind(req.BusId)
	} else {
		busIds := impl.usbIpServerCmd.List()
		for _, item := range busIds {
			impl.usbIpServerCmd.UnBind(item.BusId)
		}
	}

	return &dto.UsbipUnbindRsp{}, nil
}

func (impl *HttpMsgHandleServiceImpl) DispatchList(req *dto.DispatchListReq) (*dto.DispatchListRsp, error) {
	rets := impl.tcpsessionMgr.GetAllList()

	if req.DispatchId != "" {
		find := false
		for _, item := range rets {
			if item.DispatchId == req.DispatchId {
				rets = []*vo.DispatchInfo{item}
				find = true
				break
			}
		}
		if !find {
			rets = []*vo.DispatchInfo{}
		}
	}

	for _, item := range rets {
		busInfos := impl.usbipServerCliMgr.GetBusIdsByConnId(item.ConnId)
		item.BusInfos = busInfos
	}

	return &dto.DispatchListRsp{
		Data: map[string]any{
			"list": rets,
		},
	}, nil
}

func (impl *HttpMsgHandleServiceImpl) ClientInfo(req *dto.ClientInfoReq) (*dto.ClientInfoRsp, error) {
	log.Infof("ClientInfo req:%#v", req)
	clientMsg := &pb.ProxyMsg{
		MsgType: pb.MsgType_TO_CLIENT_INFO,
		Base:    &pb.BaseParam{},
		Param:   &pb.ProxyMsg_ToClientInfoParam{ToClientInfoParam: &pb.ToClientInfoParam{}},
	}

	var clientInfoRsp *dto.ClientInfoRsp
	ret, err := impl.tcpClientSvc.SendOutMsgByDispatchId(clientMsg, req.DispatchId)
	if ret != nil {
		json.Unmarshal(ret.Data, &clientInfoRsp)
	}
	return clientInfoRsp, err
}

func (impl *HttpMsgHandleServiceImpl) UsbIpDetach(req *dto.UsbIpDetachReq) (*dto.UsbIpDetachRsp, error) {
	log.Infof("UsbIpDetach req:%#v", req)
	var (
		sessId   string
		vendorId string
	)
	dispatchCli := impl.tcpsessionMgr.GetByDid(req.DispatchId)
	if dispatchCli.GetConnId() > 0 {
		busInfos := impl.usbipServerCliMgr.GetBusIdsByConnId(dispatchCli.GetConnId())
		for _, item := range busInfos {
			if item.BusId == req.BusId {
				sessId = item.SessId
				vendorId = item.VendorId
				break
			}
		}
	} else {
		dispatchList := impl.tcpsessionMgr.GetAllList()
		for _, item := range dispatchList {
			busInfos := impl.usbipServerCliMgr.GetBusIdsByConnId(item.ConnId)
			for _, busInfo := range busInfos {
				if busInfo.BusId == req.BusId {
					sessId = busInfo.SessId
					break
				}
			}
		}
	}

	time.AfterFunc(time.Second*6, func() {
		toServerMsg := &pb.ProxyMsg{
			MsgType: pb.MsgType_TO_SERVER_DETACH,
			Param: &pb.ProxyMsg_ToServerDetachParam{ToServerDetachParam: &pb.ToServerDetachParam{
				SessId: sessId,
				BusId:  req.BusId,
			}},
		}

		impl.tcpClientSvc.RecvInMsgByDispatchId(toServerMsg, req.DispatchId)
		impl.statusMgr.Remove(req.BusId)
	})

	toClientMsg := &pb.ProxyMsg{
		MsgType: pb.MsgType_TO_CLIENT_DETACH,
		Param: &pb.ProxyMsg_ToClientDetachParam{ToClientDetachParam: &pb.ToClientDetachParam{
			SessId:   sessId,
			BusId:    req.BusId,
			VendorId: vendorId,
		}},
	}

	impl.tcpClientSvc.SendOutMsgByDispatchIdWithNoReply(toClientMsg, req.DispatchId)

	return &dto.UsbIpDetachRsp{}, nil

}

func (impl *HttpMsgHandleServiceImpl) UsbIpAttach(req *dto.UsbIpAttachReq) (*dto.UsbIpAttachRsp, error) {
	log.Infof("UsbIpAttach req:%#v", req)

	statusInfo := impl.statusMgr.Get(req.BusId)
	if statusInfo != nil {
		nowUtc := uint32(time.Now().Unix())
		if statusInfo.AttachUtc > 0 && statusInfo.AttachUtc+10 >= nowUtc {
			return &dto.UsbIpAttachRsp{
				Code: 1,
				Msg:  "attaching",
			}, nil
		}
	}

	vendorId := ""
	//检查bus id 存不存在
	usbIpList := impl.usbIpServerCmd.List()
	for _, item := range usbIpList {
		if item.BusId == req.BusId {
			vendorId = item.VendorId
			break
		}
	}

	if vendorId == "" {
		return &dto.UsbIpAttachRsp{
			Code: dto.CodeBusIdNotExist,
			Msg:  dto.CodeBusIdNotExistDesc,
		}, nil
	}

	_, _, _, exist := impl.usbipServerCliMgr.GetByBusId(req.BusId)
	if exist {
		return &dto.UsbIpAttachRsp{
			Code: dto.CodeAttachBusIdHasBeAttachEd,
			Msg:  dto.CodeAttachBusIdHasBeAttachEdDesc,
		}, nil
	}

	impl.usbIpServerCmd.UnBind(req.BusId)
	impl.usbIpServerCmd.Bind(req.BusId)

	toClientMsg := &pb.ProxyMsg{
		MsgType: pb.MsgType_TO_CLIENT_ATTACH,
		Param: &pb.ProxyMsg_ToClientAttachParam{ToClientAttachParam: &pb.ToClientAttachParam{
			BusId:    req.BusId,
			VendorId: vendorId,
		}},
	}

	impl.tcpClientSvc.SendOutMsgByDispatchIdWithNoReply(toClientMsg, req.DispatchId)
	impl.statusMgr.Add(req.BusId, vendorId)

	return &dto.UsbIpAttachRsp{}, nil
}

func (impl *HttpMsgHandleServiceImpl) UsbIpList(msg *dto.UsbIpListReq) (*dto.UsbIpListRsp, error) {
	result := impl.usbIpServerCmd.List()

	var (
		data      dto.UsbIpListRspData
		vids      []string
		pids      []string
		deviceMap map[string][]*vo.DeviceInfo
	)

	for _, item := range result {
		vid, pid, err := common.ExtractVidPid(item.VendorId)
		if err != nil {
			log.Errorf("ExtractVidPid vendorId:%s error: %v", item.VendorId, err)
			//continue
		}
		vids = append(vids, vid)
		pids = append(pids, pid)
	}

	deviceMap = impl.getDeviceInfo(vids, pids)

outerLoop:
	for idx, item := range result {
		info := dto.UsbIpListRspDataItem{
			BusId:    item.BusId,
			VendorId: item.VendorId,
			Vendor:   item.Vendor,
			Product:  item.Product,
			Status:   common.UsbIpStatusOK,
		}
		connId, _, sessId, exist := impl.usbipServerCliMgr.GetByBusId(item.BusId)
		if exist {
			info.Status = common.UsbIpStatusAttached
			info.ConnId = connId
			info.SessId = sessId
		}

		//vid, pid, _ := common.ExtractVidPid(item.VendorId)
		deviceInfos := deviceMap[impl.makeVidPidKey(vids[idx], pids[idx])]
		for _, device := range deviceInfos {
			log.Debugf("device:%#v", device)
			if device.PnpClass == common.PnpClassKeyboard ||
				device.PnpClass == common.PnpClassMouse ||
				device.PnpClass == common.PnpClassMassStorage { /*||
				device.PnpClass == common.PnpClassHIDClass*/
				continue outerLoop
			}
		}

		var (
			names         []string
			describes     []string
			manufacturers []string
		)

		for i := len(deviceInfos) - 1; i >= 0; i-- {
			if deviceInfos[i].DeviceName != "" {
				names = append(names, deviceInfos[i].DeviceName)
			}

			if deviceInfos[i].DeviceDesc != "" {
				describes = append(describes, deviceInfos[i].DeviceDesc)
			}

			if deviceInfos[i].Manufacturer != "" {
				manufacturers = append(manufacturers, deviceInfos[i].Manufacturer)
			}
		}

		if strings.Contains(strings.ToLower(info.Vendor), "unknown") {
			if len(manufacturers) > 0 {
				info.Vendor = strings.Join(manufacturers, ",")
			} else if len(names) > 0 {
				info.Vendor = strings.Join(names, ",")
			} else if len(describes) > 0 {
				info.Vendor = strings.Join(describes, ",")
			}
		}

		if strings.Contains(strings.ToLower(info.Product), "unknown") {
			if runtime.GOOS == "windows" {
				tmps := slice.Concat(manufacturers, names, describes)
				tmps = slice.Unique(tmps)
				info.Product = strings.Join(tmps, ",")
			} else {
				if len(describes) > 0 {
					info.Product = strings.Join(describes, ",")
				} else if len(names) > 0 {
					info.Product = strings.Join(names, ",")
				}
			}
		}

		data.List = append(data.List, info)
	}

	rets := impl.tcpsessionMgr.GetAllList()
	for i := range data.List {
		for _, item := range rets {
			if item.ConnId == data.List[i].ConnId {
				data.List[i].DispatchId = item.DispatchId
				break
			}
		}
	}

	return &dto.UsbIpListRsp{
		Data: data,
	}, nil
}

func (impl *HttpMsgHandleServiceImpl) DeviceInfo(req *dto.DeviceInfoReq) (*dto.DeviceInfoRsp, error) {
	log.Infof("DeviceInfo req:%#v", req)

	result := impl.usbIpServerCmd.List()
	var deviceRsp dto.DeviceInfoRsp
	var datas []any
	for _, item := range result {

		if req.BusId != "" && item.BusId != req.BusId {
			continue
		}

		vid, pid, err := common.ExtractVidPid(item.VendorId)
		if err != nil {
			log.Errorf("ExtractVidPid vendorId:%s error: %v", item.VendorId, err)
		}
		deviceInfos := impl.deviceSvc.GetDeviceInfo(vid, pid)
		datas = append(datas, map[string]any{
			"bus_id":      item.BusId,
			"vendor_id":   item.VendorId,
			"device_info": deviceInfos,
		})
	}

	deviceRsp.Data = map[string]any{
		"list": datas,
	}

	return &deviceRsp, nil
}

func (impl *HttpMsgHandleServiceImpl) makeVidPidKey(vid, pid string) string {
	return fmt.Sprintf("%s:%s", vid, pid)
}

func (impl *HttpMsgHandleServiceImpl) getDeviceInfo(vids, pids []string) map[string][]*vo.DeviceInfo {
	if len(vids) != len(pids) {
		return nil
	}
	log.Debugf("getDeviceInfo len(vids):%d, len(pids):%d", len(vids), len(pids))
	var wg sync.WaitGroup
	var lock sync.Mutex
	retMap := make(map[string][]*vo.DeviceInfo)
	for idx := range vids {
		key := impl.makeVidPidKey(vids[idx], pids[idx])
		wg.Add(1)
		go func() {
			defer wg.Done()
			deviceInfos := impl.deviceSvc.GetDeviceInfo(vids[idx], pids[idx])
			lock.Lock()
			retMap[key] = deviceInfos
			lock.Unlock()
		}()
	}

	wg.Wait()
	log.Debugf("getDeviceInfo len(vids):%d, len(pids):%d over", len(vids), len(pids))
	return retMap
}

func NewHttpMsgHandleServiceImpl(tcpClientSvc TcpClientService,
	usbIpServerCmd service.UsbIpServerCmdService,
	tcpsessionMgr tcpsession.TcpSessionMgrRepo,
	usbipServerCliMgr usbipserversrv.UsbipServerSrvMgrService,
	deviceSvc service.DeviceService,
	statusMgr usbipstatusmgr.UsbIpStatusMgrService) HttpMsgHandleService {
	return &HttpMsgHandleServiceImpl{
		tcpClientSvc:      tcpClientSvc,
		usbIpServerCmd:    usbIpServerCmd,
		usbipServerCliMgr: usbipServerCliMgr,
		tcpsessionMgr:     tcpsessionMgr,
		deviceSvc:         deviceSvc,
		statusMgr:         statusMgr,
	}
}
